/**
 *   DataSlim - Lazy Loading Framework
 *   Copyright (C) 2007 Rodolfo Dias
 *   
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License along
 *   with this program; if not, write to the Free Software Foundation, Inc.,
 *   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * 
 */

package net.dataslim.processor;

import java.io.PrintWriter;
import java.net.URL;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import com.sun.mirror.apt.AnnotationProcessorEnvironment;
import com.sun.mirror.declaration.AnnotationMirror;
import com.sun.mirror.declaration.ClassDeclaration;
import com.sun.mirror.declaration.MethodDeclaration;
import com.sun.mirror.declaration.Modifier;
import com.sun.mirror.declaration.ParameterDeclaration;
import com.sun.mirror.type.ReferenceType;

public class GeneratorDataSlimClass extends SimpleTemplateVisitor {

	ClassDeclaration classDeclaration;
	AnnotationProcessorEnvironment env;
	
	String newPackage = null;
	String newClass   = null;
	
	StringBuffer newSource = new StringBuffer();
	StringBuffer bufferMethods = null;

	
	
	public GeneratorDataSlimClass(
			ClassDeclaration classDeclaration, 
			AnnotationProcessorEnvironment env){
		super(env);
		this.classDeclaration = classDeclaration;
		this.env = env;
	}
	
	
	protected String visitorLine(String line){
	    String breakLine = System.getProperty("line.separator");
	 	if (line.trim().contains("class.methods") || bufferMethods != null){
			if (bufferMethods == null){
				bufferMethods = new StringBuffer("");
			    return line;
			}
			if (line.trim().startsWith("</")){	
				line = visitorMethods(bufferMethods.toString());
				newSource.append(line);
				newSource.append(breakLine);
				bufferMethods = null;
			}else{
				bufferMethods.append(super.visitorLine(line));
				bufferMethods.append(breakLine);
			}
		}else{
			line = super.visitorLine(line);
			newSource.append(line);
			newSource.append(breakLine);
		}
	 	return line;
	 } 
	
	public void endTemplate(){
		
		System.out.println(".........Gerando Codigo....................");
		System.out.println(newPackage + ".dataslim." + newClass+"DataSlim");
		try{
			PrintWriter writer = env.getFiler().createSourceFile(newPackage + ".dataslim." + newClass+"DataSlim");
			writer.append(newSource.toString());
			System.out.println(".........Codigo Gerado.................");
		}catch(Exception e){
			e.printStackTrace();
		}
	}
	
	
	public String visitorPackage(String line){
		String packageName = classDeclaration.getPackage().getQualifiedName();
		this.newPackage = packageName;
		return replace(line, "<package.qualifiedName/>", packageName);
	}
	
	public String visitorNameClass(String line){
		String nameClass = classDeclaration.getSimpleName();
		this.newClass = nameClass;
		return replace(line, "<class.simpleName/>", nameClass);
	}
	
	public String visitorTypeClass(String line){
		return "";
	}
	
	public String visitorField(String line){
		String nameField = classDeclaration.getSimpleName().toLowerCase();
		return replace(line, "<class.simpleName.field/>", nameField);
	}
	

	
	public String visitorMethods(String line){
		StringBuffer sourceString = new StringBuffer("");
		Map<String, MethodDeclaration> mapMethods = new HashMap<String, MethodDeclaration>();
		addAll(mapMethods, classDeclaration.getMethods());
		setHierarquicalMethods( classDeclaration, mapMethods );
		for(MethodDeclaration m : mapMethods.values()){
			if (!isMethodAbstractOrPrivate(m)){
				//verifica os anotations presente no metodo
				Collection<AnnotationMirror> annotations = m.getAnnotationMirrors();
				boolean processLazy = false;
				for(AnnotationMirror mirror : annotations){
					if(mirror.getAnnotationType().getDeclaration().getSimpleName().equals("DataSlimLazy")){
							sourceString.append(
									generatorMethodBody(m, mirror, line));
							processLazy = true;
					}
				}
				if (!processLazy){
					sourceString.append(generatorMethodBody(m));
				}
			}				
		}
		return sourceString.toString();
	}
	
	private void generateModiferMethod(MethodDeclaration m, StringBuffer methodBuffer){
		Collection<Modifier> modifiers = m.getModifiers();
		for(Modifier mod : modifiers){
			methodBuffer.append(mod + " ");
		}
	}
	
	private void generateParameterMethod(MethodDeclaration m, StringBuffer methodBuffer){
		methodBuffer.append("(");
		Collection<ParameterDeclaration> params = m.getParameters();
		int count = 0;
		for(ParameterDeclaration p : params){
			methodBuffer.append(p.getType() + " " + p.getSimpleName());
			count++;
			if (count != params.size()){
				methodBuffer.append(",");
			}
		}
		methodBuffer.append(")");
	}
	
	private void generateThrowsMethod(MethodDeclaration m, StringBuffer methodBuffer){
		int count = 0;
		Collection<ReferenceType> throwsExistents = m.getThrownTypes();
		if (!throwsExistents.isEmpty()){
			int sizeThrows = throwsExistents.size();
			methodBuffer.append("throws ");
			for(ReferenceType tRef : throwsExistents){
				tRef.accept( new ThrowsTypeVisitor(env, methodBuffer) );
				count++;
				if (count != sizeThrows){
					methodBuffer.append(",");
				}
			}
		}
	}
	
	private String generatorMethodBody(MethodDeclaration m, AnnotationMirror dataSlimLazy, String line){
		String breakLine = System.getProperty("line.separator");
		StringBuffer methodBuffer = new StringBuffer("");
		generateModiferMethod(m, methodBuffer);
		methodBuffer.append(m.getReturnType() + " ");
		methodBuffer.append(m.getSimpleName() );
		generateParameterMethod(m, methodBuffer);
		methodBuffer.append("{ " + breakLine);
		line = replace(line, "<method.simpleName/>", m.getSimpleName());
		line = replace(line, "<method.returnType/>", m.getReturnType().toString());
		methodBuffer.append(line);
		methodBuffer.append("}"+breakLine);
		return methodBuffer.toString();
	}
	
	private String generatorMethodBody(MethodDeclaration m){
		String breakLine = System.getProperty("line.separator");
		StringBuffer methodBuffer = new StringBuffer("");
		String line = super.visitorLine("this.<class.simpleName.field/>.<method.simpleName/>(");
		line = replace(line, "<method.simpleName/>", m.getSimpleName());
		generateModiferMethod(m, methodBuffer);
		methodBuffer.append(m.getReturnType() + " ");
		methodBuffer.append(m.getSimpleName() + "(");
		Collection<ParameterDeclaration> params = m.getParameters();
		int count = 0;
		for(ParameterDeclaration p : params){
			methodBuffer.append(p.getType() + " " + p.getSimpleName());
			line += p.getSimpleName();
			count++;
			if (count != params.size()){
				methodBuffer.append(",");
				line += ",";
			}
		}
		line += ")";
		methodBuffer.append(") ");
		generateThrowsMethod(m, methodBuffer);
		methodBuffer.append("{ " + breakLine);
		if (!m.getReturnType().toString().equals("void")){
			methodBuffer.append("return ");
		}
		methodBuffer.append(line+";"+breakLine);
		methodBuffer.append("}"+breakLine);
		return methodBuffer.toString();
	}
	
	public URL getTemplate(){
		URL url = null;
		try{
			return this.getClass().getResource("/Proxied.template");
			//url = new URL("file", "",  PATH_TEMPLATE +"/Proxied.template");
		}catch(Exception e){
			e.printStackTrace();
		}
		return url;
	}
	
	/**
	 * Metodo responsavel por navegar em todos os metodos da hierarquia de classe
	 * e  guarda-las numa colecao.
	 * 
	 * @param clazzDeclaration
	 * @param mapMethods
	 */
	private void setHierarquicalMethods(ClassDeclaration clazzDeclaration, Map<String, MethodDeclaration> mapMethods){
		ClassDeclaration superClassDeclaration = null;
		if ( clazzDeclaration.getSuperclass() != null){
			superClassDeclaration = clazzDeclaration.getSuperclass().getDeclaration();
			if (superClassDeclaration == null || superClassDeclaration.getSimpleName().equals("Object")){
				return; //Fim da linha :)
			}
			addAll(mapMethods,  superClassDeclaration.getMethods() );
			setHierarquicalMethods(superClassDeclaration, mapMethods);
		}
	}
	
	/**
	 * Metodo resposavel por manter a unicidade de metodos na colecao
	 * 
	 * @param map
	 * @param c
	 */
	private void addAll(Map<String, MethodDeclaration> map, Collection<MethodDeclaration> c){
		Iterator<MethodDeclaration> e = c.iterator();
		while (e.hasNext()) {
			MethodDeclaration methodDeclaration = e.next();
			String key = methodDeclaration.getSimpleName();
			for(ParameterDeclaration p : methodDeclaration.getParameters()){
				key +=p.getType().toString();
			}
		    map.put(key, methodDeclaration);
		}
	}
	
	private boolean isMethodAbstractOrPrivate(MethodDeclaration m){
		boolean ret = false;
		Collection<Modifier> c = m.getModifiers();
		for(Modifier mod : c){
			if (mod.toString().equals("abstract") || mod.toString().equals("private")){
				return true;
			}
		}
		return ret;
	}
}
